home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / stk-3.002 / stk-3 / STk-3.1 / Tk / generic / tkSquare.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-31  |  17.0 KB  |  589 lines

  1. /* 
  2.  * tkSquare.c --
  3.  *
  4.  *    This module implements "square" widgets.  A "square" is
  5.  *    a widget that displays a single square that can be moved
  6.  *    around and resized.  This file is intended as an example
  7.  *    of how to build a widget;  it isn't included in the
  8.  *    normal wish, but it is included in "tktest".
  9.  *
  10.  * Copyright (c) 1991-1994 The Regents of the University of California.
  11.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  12.  *
  13.  * See the file "license.terms" for information on usage and redistribution
  14.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  15.  *
  16.  * SCCS: @(#) tkSquare.c 1.16 96/02/15 18:52:44
  17.  */
  18.  
  19. #include "tkPort.h"
  20. #include "tk.h"
  21.  
  22. /*
  23.  * A data structure of the following type is kept for each square
  24.  * widget managed by this file:
  25.  */
  26.  
  27. typedef struct {
  28.     Tk_Window tkwin;        /* Window that embodies the square.  NULL
  29.                  * means window has been deleted but
  30.                  * widget record hasn't been cleaned up yet. */
  31.     Display *display;        /* X's token for the window's display. */
  32.     Tcl_Interp *interp;        /* Interpreter associated with widget. */
  33.     Tcl_Command widgetCmd;    /* Token for square's widget command. */
  34.     int x, y;            /* Position of square's upper-left corner
  35.                  * within widget. */
  36.     int size;            /* Width and height of square. */
  37.  
  38.     /*
  39.      * Information used when displaying widget:
  40.      */
  41.  
  42.     int borderWidth;        /* Width of 3-D border around whole widget. */
  43.     Tk_3DBorder bgBorder;    /* Used for drawing background. */
  44.     Tk_3DBorder fgBorder;    /* For drawing square. */
  45.     int relief;            /* Indicates whether window as a whole is
  46.                  * raised, sunken, or flat. */
  47.     GC gc;            /* Graphics context for copying from
  48.                  * off-screen pixmap onto screen. */
  49.     int doubleBuffer;        /* Non-zero means double-buffer redisplay
  50.                  * with pixmap;  zero means draw straight
  51.                  * onto the display. */
  52.     int updatePending;        /* Non-zero means a call to SquareDisplay
  53.                  * has already been scheduled. */
  54. } Square;
  55.  
  56. /*
  57.  * Information used for argv parsing.
  58.  */
  59.  
  60. static Tk_ConfigSpec configSpecs[] = {
  61.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  62.     "#cdb79e", Tk_Offset(Square, bgBorder), TK_CONFIG_COLOR_ONLY},
  63.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  64.     "white", Tk_Offset(Square, bgBorder), TK_CONFIG_MONO_ONLY},
  65.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  66.     (char *) NULL, 0, 0},
  67.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  68.     (char *) NULL, 0, 0},
  69.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  70.     "2", Tk_Offset(Square, borderWidth), 0},
  71.     {TK_CONFIG_INT, "-dbl", "doubleBuffer", "DoubleBuffer",
  72.     "1", Tk_Offset(Square, doubleBuffer), 0},
  73.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  74.     (char *) NULL, 0, 0},
  75.     {TK_CONFIG_BORDER, "-foreground", "foreground", "Foreground",
  76.     "#b03060", Tk_Offset(Square, fgBorder), TK_CONFIG_COLOR_ONLY},
  77.     {TK_CONFIG_BORDER, "-foreground", "foreground", "Foreground",
  78.     "black", Tk_Offset(Square, fgBorder), TK_CONFIG_MONO_ONLY},
  79.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  80.     "raised", Tk_Offset(Square, relief), 0},
  81.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  82.     (char *) NULL, 0, 0}
  83. };
  84.  
  85. /*
  86.  * Forward declarations for procedures defined later in this file:
  87.  */
  88.  
  89. int            SquareCmd _ANSI_ARGS_((ClientData clientData,
  90.                 Tcl_Interp *interp, int argc, char **argv));
  91. static void        SquareCmdDeletedProc _ANSI_ARGS_((
  92.                 ClientData clientData));
  93. static int        SquareConfigure _ANSI_ARGS_((Tcl_Interp *interp,
  94.                 Square *squarePtr, int argc, char **argv,
  95.                 int flags));
  96. static void        SquareDestroy _ANSI_ARGS_((char *memPtr));
  97. static void        SquareDisplay _ANSI_ARGS_((ClientData clientData));
  98. static void        KeepInWindow _ANSI_ARGS_((Square *squarePtr));
  99. static void        SquareEventProc _ANSI_ARGS_((ClientData clientData,
  100.                 XEvent *eventPtr));
  101. static int        SquareWidgetCmd _ANSI_ARGS_((ClientData clientData,
  102.                 Tcl_Interp *, int argc, char **argv));
  103.  
  104. /*
  105.  *--------------------------------------------------------------
  106.  *
  107.  * SquareCmd --
  108.  *
  109.  *    This procedure is invoked to process the "square" Tcl
  110.  *    command.  It creates a new "square" widget.
  111.  *
  112.  * Results:
  113.  *    A standard Tcl result.
  114.  *
  115.  * Side effects:
  116.  *    A new widget is created and configured.
  117.  *
  118.  *--------------------------------------------------------------
  119.  */
  120.  
  121. int
  122. SquareCmd(clientData, interp, argc, argv)
  123.     ClientData clientData;    /* Main window associated with
  124.                  * interpreter. */
  125.     Tcl_Interp *interp;        /* Current interpreter. */
  126.     int argc;            /* Number of arguments. */
  127.     char **argv;        /* Argument strings. */
  128. {
  129.     Tk_Window main = (Tk_Window) clientData;
  130.     Square *squarePtr;
  131.     Tk_Window tkwin;
  132.  
  133.     if (argc < 2) {
  134.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  135.         argv[0], " pathName ?options?\"", (char *) NULL);
  136.     return TCL_ERROR;
  137.     }
  138.  
  139.     tkwin = Tk_CreateWindowFromPath(interp, main, argv[1], (char *) NULL);
  140.     if (tkwin == NULL) {
  141.     return TCL_ERROR;
  142.     }
  143.     Tk_SetClass(tkwin, "Square");
  144.  
  145.     /*
  146.      * Allocate and initialize the widget record.
  147.      */
  148.  
  149.     squarePtr = (Square *) ckalloc(sizeof(Square));
  150.     squarePtr->tkwin = tkwin;
  151.     squarePtr->display = Tk_Display(tkwin);
  152.     squarePtr->interp = interp;
  153.     squarePtr->widgetCmd = Tcl_CreateCommand(interp,
  154.         Tk_PathName(squarePtr->tkwin), SquareWidgetCmd,
  155.         (ClientData) squarePtr, SquareCmdDeletedProc);
  156.     squarePtr->x = 0;
  157.     squarePtr->y = 0;
  158.     squarePtr->size = 20;
  159.     squarePtr->borderWidth = 0;
  160.     squarePtr->bgBorder = NULL;
  161.     squarePtr->fgBorder = NULL;
  162.     squarePtr->relief = TK_RELIEF_FLAT;
  163.     squarePtr->gc = None;
  164.     squarePtr->doubleBuffer = 1;
  165.     squarePtr->updatePending = 0;
  166.  
  167.     Tk_CreateEventHandler(squarePtr->tkwin, ExposureMask|StructureNotifyMask,
  168.         SquareEventProc, (ClientData) squarePtr);
  169.     if (SquareConfigure(interp, squarePtr, argc-2, argv+2, 0) != TCL_OK) {
  170.     Tk_DestroyWindow(squarePtr->tkwin);
  171.     return TCL_ERROR;
  172.     }
  173.  
  174.     interp->result = Tk_PathName(squarePtr->tkwin);
  175.     return TCL_OK;
  176. }
  177.  
  178. /*
  179.  *--------------------------------------------------------------
  180.  *
  181.  * SquareWidgetCmd --
  182.  *
  183.  *    This procedure is invoked to process the Tcl command
  184.  *    that corresponds to a widget managed by this module.
  185.  *    See the user documentation for details on what it does.
  186.  *
  187.  * Results:
  188.  *    A standard Tcl result.
  189.  *
  190.  * Side effects:
  191.  *    See the user documentation.
  192.  *
  193.  *--------------------------------------------------------------
  194.  */
  195.  
  196. static int
  197. SquareWidgetCmd(clientData, interp, argc, argv)
  198.     ClientData clientData;        /* Information about square widget. */
  199.     Tcl_Interp *interp;            /* Current interpreter. */
  200.     int argc;                /* Number of arguments. */
  201.     char **argv;            /* Argument strings. */
  202. {
  203.     Square *squarePtr = (Square *) clientData;
  204.     int result = TCL_OK;
  205.     size_t length;
  206.     char c;
  207.  
  208.     if (argc < 2) {
  209.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  210.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  211.     return TCL_ERROR;
  212.     }
  213.     Tcl_Preserve((ClientData) squarePtr);
  214.     c = argv[1][0];
  215.     length = strlen(argv[1]);
  216.     if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  217.         && (length >= 2)) {
  218.     if (argc != 3) {
  219.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  220.             argv[0], " cget option\"",
  221.             (char *) NULL);
  222.         goto error;
  223.     }
  224.     result = Tk_ConfigureValue(interp, squarePtr->tkwin, configSpecs,
  225.         (char *) squarePtr, argv[2], 0);
  226.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  227.         && (length >= 2)) {
  228.     if (argc == 2) {
  229.         result = Tk_ConfigureInfo(interp, squarePtr->tkwin, configSpecs,
  230.             (char *) squarePtr, (char *) NULL, 0);
  231.     } else if (argc == 3) {
  232.         result = Tk_ConfigureInfo(interp, squarePtr->tkwin, configSpecs,
  233.             (char *) squarePtr, argv[2], 0);
  234.     } else {
  235.         result = SquareConfigure(interp, squarePtr, argc-2, argv+2,
  236.             TK_CONFIG_ARGV_ONLY);
  237.     }
  238.     } else if ((c == 'p') && (strncmp(argv[1], "position", length) == 0)) {
  239.     if ((argc != 2) && (argc != 4)) {
  240.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  241.             argv[0], " position ?x y?\"", (char *) NULL);
  242.         goto error;
  243.     }
  244.     if (argc == 4) {
  245.         if ((Tk_GetPixels(interp, squarePtr->tkwin, argv[2],
  246.             &squarePtr->x) != TCL_OK) || (Tk_GetPixels(interp,
  247.             squarePtr->tkwin, argv[3], &squarePtr->y) != TCL_OK)) {
  248.         goto error;
  249.         }
  250.         KeepInWindow(squarePtr);
  251.     }
  252.     sprintf(interp->result, "%d %d", squarePtr->x, squarePtr->y);
  253.     } else if ((c == 's') && (strncmp(argv[1], "size", length) == 0)) {
  254.     if ((argc != 2) && (argc != 3)) {
  255.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  256.             argv[0], " size ?amount?\"", (char *) NULL);
  257.         goto error;
  258.     }
  259.     if (argc == 3) {
  260.         int i;
  261.  
  262.         if (Tk_GetPixels(interp, squarePtr->tkwin, argv[2], &i) != TCL_OK) {
  263.         goto error;
  264.         }
  265.         if ((i <= 0) || (i > 100)) {
  266.         Tcl_AppendResult(interp, "bad size \"", argv[2],
  267.             "\"", (char *) NULL);
  268.         goto error;
  269.         }
  270.         squarePtr->size = i;
  271.         KeepInWindow(squarePtr);
  272.     }
  273.     sprintf(interp->result, "%d", squarePtr->size);
  274.     } else {
  275.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  276.         "\": must be cget, configure, position, or size",
  277.         (char *) NULL);
  278.     goto error;
  279.     }
  280.     if (!squarePtr->updatePending) {
  281.     Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
  282.     squarePtr->updatePending = 1;
  283.     }
  284.     Tcl_Release((ClientData) squarePtr);
  285.     return result;
  286.  
  287.     error:
  288.     Tcl_Release((ClientData) squarePtr);
  289.     return TCL_ERROR;
  290. }
  291.  
  292. /*
  293.  *----------------------------------------------------------------------
  294.  *
  295.  * SquareConfigure --
  296.  *
  297.  *    This procedure is called to process an argv/argc list in
  298.  *    conjunction with the Tk option database to configure (or
  299.  *    reconfigure) a square widget.
  300.  *
  301.  * Results:
  302.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  303.  *    returned, then interp->result contains an error message.
  304.  *
  305.  * Side effects:
  306.  *    Configuration information, such as colors, border width,
  307.  *    etc. get set for squarePtr;  old resources get freed,
  308.  *    if there were any.
  309.  *
  310.  *----------------------------------------------------------------------
  311.  */
  312.  
  313. static int
  314. SquareConfigure(interp, squarePtr, argc, argv, flags)
  315.     Tcl_Interp *interp;            /* Used for error reporting. */
  316.     Square *squarePtr;            /* Information about widget. */
  317.     int argc;                /* Number of valid entries in argv. */
  318.     char **argv;            /* Arguments. */
  319.     int flags;                /* Flags to pass to
  320.                      * Tk_ConfigureWidget. */
  321. {
  322.     if (Tk_ConfigureWidget(interp, squarePtr->tkwin, configSpecs,
  323.         argc, argv, (char *) squarePtr, flags) != TCL_OK) {
  324.     return TCL_ERROR;
  325.     }
  326.  
  327.     /*
  328.      * Set the background for the window and create a graphics context
  329.      * for use during redisplay.
  330.      */
  331.  
  332.     Tk_SetWindowBackground(squarePtr->tkwin,
  333.         Tk_3DBorderColor(squarePtr->bgBorder)->pixel);
  334.     if ((squarePtr->gc == None) && (squarePtr->doubleBuffer)) {
  335.     XGCValues gcValues;
  336.     gcValues.function = GXcopy;
  337.     gcValues.graphics_exposures = False;
  338.     squarePtr->gc = Tk_GetGC(squarePtr->tkwin,
  339.         GCFunction|GCGraphicsExposures, &gcValues);
  340.     }
  341.  
  342.     /*
  343.      * Register the desired geometry for the window.  Then arrange for
  344.      * the window to be redisplayed.
  345.      */
  346.  
  347.     Tk_GeometryRequest(squarePtr->tkwin, 200, 150);
  348.     Tk_SetInternalBorder(squarePtr->tkwin, squarePtr->borderWidth);
  349.     if (!squarePtr->updatePending) {
  350.     Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
  351.     squarePtr->updatePending = 1;
  352.     }
  353.     return TCL_OK;
  354. }
  355.  
  356. /*
  357.  *--------------------------------------------------------------
  358.  *
  359.  * SquareEventProc --
  360.  *
  361.  *    This procedure is invoked by the Tk dispatcher for various
  362.  *    events on squares.
  363.  *
  364.  * Results:
  365.  *    None.
  366.  *
  367.  * Side effects:
  368.  *    When the window gets deleted, internal structures get
  369.  *    cleaned up.  When it gets exposed, it is redisplayed.
  370.  *
  371.  *--------------------------------------------------------------
  372.  */
  373.  
  374. static void
  375. SquareEventProc(clientData, eventPtr)
  376.     ClientData clientData;    /* Information about window. */
  377.     XEvent *eventPtr;        /* Information about event. */
  378. {
  379.     Square *squarePtr = (Square *) clientData;
  380.  
  381.     if (eventPtr->type == Expose) {
  382.     if (!squarePtr->updatePending) {
  383.         Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
  384.         squarePtr->updatePending = 1;
  385.     }
  386.     } else if (eventPtr->type == ConfigureNotify) {
  387.     KeepInWindow(squarePtr);
  388.     if (!squarePtr->updatePending) {
  389.         Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
  390.         squarePtr->updatePending = 1;
  391.     }
  392.     } else if (eventPtr->type == DestroyNotify) {
  393.     if (squarePtr->tkwin != NULL) {
  394.         squarePtr->tkwin = NULL;
  395.         Tcl_DeleteCommand(squarePtr->interp,
  396.             Tcl_GetCommandName(squarePtr->interp,
  397.             squarePtr->widgetCmd));
  398.     }
  399.     if (squarePtr->updatePending) {
  400.         Tcl_CancelIdleCall(SquareDisplay, (ClientData) squarePtr);
  401.     }
  402.     Tcl_EventuallyFree((ClientData) squarePtr, SquareDestroy);
  403.     }
  404. }
  405.  
  406. /*
  407.  *----------------------------------------------------------------------
  408.  *
  409.  * SquareCmdDeletedProc --
  410.  *
  411.  *    This procedure is invoked when a widget command is deleted.  If
  412.  *    the widget isn't already in the process of being destroyed,
  413.  *    this command destroys it.
  414.  *
  415.  * Results:
  416.  *    None.
  417.  *
  418.  * Side effects:
  419.  *    The widget is destroyed.
  420.  *
  421.  *----------------------------------------------------------------------
  422.  */
  423.  
  424. static void
  425. SquareCmdDeletedProc(clientData)
  426.     ClientData clientData;    /* Pointer to widget record for widget. */
  427. {
  428.     Square *squarePtr = (Square *) clientData;
  429.     Tk_Window tkwin = squarePtr->tkwin;
  430.  
  431.     /*
  432.      * This procedure could be invoked either because the window was
  433.      * destroyed and the command was then deleted (in which case tkwin
  434.      * is NULL) or because the command was deleted, and then this procedure
  435.      * destroys the widget.
  436.      */
  437.  
  438.     if (tkwin != NULL) {
  439.     squarePtr->tkwin = NULL;
  440.     Tk_DestroyWindow(tkwin);
  441.     }
  442. }
  443.  
  444. /*
  445.  *--------------------------------------------------------------
  446.  *
  447.  * SquareDisplay --
  448.  *
  449.  *    This procedure redraws the contents of a square window.
  450.  *    It is invoked as a do-when-idle handler, so it only runs
  451.  *    when there's nothing else for the application to do.
  452.  *
  453.  * Results:
  454.  *    None.
  455.  *
  456.  * Side effects:
  457.  *    Information appears on the screen.
  458.  *
  459.  *--------------------------------------------------------------
  460.  */
  461.  
  462. static void
  463. SquareDisplay(clientData)
  464.     ClientData clientData;    /* Information about window. */
  465. {
  466.     Square *squarePtr = (Square *) clientData;
  467.     Tk_Window tkwin = squarePtr->tkwin;
  468.     Pixmap pm = None;
  469.     Drawable d;
  470.  
  471.     squarePtr->updatePending = 0;
  472.     if (!Tk_IsMapped(tkwin)) {
  473.     return;
  474.     }
  475.  
  476.     /*
  477.      * Create a pixmap for double-buffering, if necessary.
  478.      */
  479.  
  480.     if (squarePtr->doubleBuffer) {
  481.     pm = XCreatePixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
  482.         (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
  483.         (unsigned) DefaultDepthOfScreen(Tk_Screen(tkwin)));
  484.     d = pm;
  485.     } else {
  486.     d = Tk_WindowId(tkwin);
  487.     }
  488.  
  489.     /*
  490.      * Redraw the widget's background and border.
  491.      */
  492.  
  493.     Tk_Fill3DRectangle(tkwin, d, squarePtr->bgBorder, 0, 0, Tk_Width(tkwin),
  494.         Tk_Height(tkwin), squarePtr->borderWidth, squarePtr->relief);
  495.  
  496.     /*
  497.      * Display the square.
  498.      */
  499.  
  500.     Tk_Fill3DRectangle(tkwin, d, squarePtr->fgBorder, squarePtr->x,
  501.         squarePtr->y, squarePtr->size, squarePtr->size,
  502.         squarePtr->borderWidth, TK_RELIEF_RAISED);
  503.  
  504.     /*
  505.      * If double-buffered, copy to the screen and release the pixmap.
  506.      */
  507.  
  508.     if (squarePtr->doubleBuffer) {
  509.     XCopyArea(Tk_Display(tkwin), pm, Tk_WindowId(tkwin), squarePtr->gc,
  510.         0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
  511.         0, 0);
  512.     XFreePixmap(Tk_Display(tkwin), pm);
  513.     }
  514. }
  515.  
  516. /*
  517.  *----------------------------------------------------------------------
  518.  *
  519.  * SquareDestroy --
  520.  *
  521.  *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
  522.  *    to clean up the internal structure of a square at a safe time
  523.  *    (when no-one is using it anymore).
  524.  *
  525.  * Results:
  526.  *    None.
  527.  *
  528.  * Side effects:
  529.  *    Everything associated with the square is freed up.
  530.  *
  531.  *----------------------------------------------------------------------
  532.  */
  533.  
  534. static void
  535. SquareDestroy(memPtr)
  536.     char *memPtr;        /* Info about square widget. */
  537. {
  538.     Square *squarePtr = (Square *) memPtr;
  539.  
  540.     Tk_FreeOptions(configSpecs, (char *) squarePtr, squarePtr->display, 0);
  541.     if (squarePtr->gc != None) {
  542.     Tk_FreeGC(squarePtr->display, squarePtr->gc);
  543.     }
  544.     ckfree((char *) squarePtr);
  545. }
  546.  
  547. /*
  548.  *----------------------------------------------------------------------
  549.  *
  550.  * KeepInWindow --
  551.  *
  552.  *    Adjust the position of the square if necessary to keep it in
  553.  *    the widget's window.
  554.  *
  555.  * Results:
  556.  *    None.
  557.  *
  558.  * Side effects:
  559.  *    The x and y position of the square are adjusted if necessary
  560.  *    to keep the square in the window.
  561.  *
  562.  *----------------------------------------------------------------------
  563.  */
  564.  
  565. static void
  566. KeepInWindow(squarePtr)
  567.     register Square *squarePtr;        /* Pointer to widget record. */
  568. {
  569.     int i, bd;
  570.     bd = 0;
  571.     if (squarePtr->relief != TK_RELIEF_FLAT) {
  572.     bd = squarePtr->borderWidth;
  573.     }
  574.     i = (Tk_Width(squarePtr->tkwin) - bd) - (squarePtr->x + squarePtr->size);
  575.     if (i < 0) {
  576.     squarePtr->x += i;
  577.     }
  578.     i = (Tk_Height(squarePtr->tkwin) - bd) - (squarePtr->y + squarePtr->size);
  579.     if (i < 0) {
  580.     squarePtr->y += i;
  581.     }
  582.     if (squarePtr->x < bd) {
  583.     squarePtr->x = bd;
  584.     }
  585.     if (squarePtr->y < bd) {
  586.     squarePtr->y = bd;
  587.     }
  588. }
  589.